﻿//-----------------------------------------------------------------------
// <copyright file="DbViewState.cs" company="n.a.">
//     Copyright Dave999. All rights reserved.
// </copyright>
//-----------------------------------------------------------------------
[module: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:ElementsMustBeDocumented", Justification = "To be returned to .")]

namespace DbView
{
	using System;
	using System.Collections.Generic;
	using System.Data;
	/*
		Reworked dec 2012

		Class to encapsulate enough state to repopulate the presenter
		and model for implementing back and history
		
		To make the state history manager easier first draft
		will parse and return strings
		
		todo: make all column names used const strings.

		Spec:
		
		Each view is added to the back chain
		when you navigate back you lose the previous ones
		History should present in last-visited order and eliminate duplicates
		
		Module Change history;
		02/07/2012: Ameliorated bug in state comparison to make the equals test even more rigourous by 
					checking command parameters
					Coincidentally added an id check as part of another bug fix

	*/
	/*
		The view state object. Should contain all the info required to rebuild a grid 
		See the properties at the start
	*/
	class DbViewState
	{
		public Command Command { get { return Command.Translate(this.command); } }
		public string ConnectionTag { get { return this.connectionTag; } }
		public string Filter { get { return this.filter; } }
		public string ViewDefName { get { return this.viewDefinitionName; } }
		public int RowIndex { get { return this.rowIndex; } set { this.rowIndex = value; } }
		public int Sequence { get { return this.sequence; } set { this.sequence = value; } }

		public DbViewState(String command, string connectionTag, string filter, string viewDefName, int rowIndex)
		{
			this.command = command;
			this.connectionTag = connectionTag;
			this.filter = filter;
			this.viewDefinitionName = viewDefName;
			this.rowIndex = rowIndex;
		}

		public override int GetHashCode()
		{
			return string.Format("{0}{1}{2}{3}", Command, this.ConnectionTag, this.Filter, this.ViewDefName).GetHashCode();
		}

		public override bool Equals(object other)
		{
			bool result = false;
			if (other is DbViewState)
			{
				DbViewState that = (DbViewState)other;
				result = this.InternalEquals(that);
			}
			return result;
		}

		private bool InternalEquals(DbViewState that)
		{
			return (this.Command.IsSameAs(that.Command, true)) && // lately extended to add a "test parameters as well" flag.
				   (this.ConnectionTag == that.ConnectionTag) &&
				   (this.Filter == that.Filter) &&
				   (this.CommandViewNameIsEqual(that));

		}

		// This to work around the case where the view def name is not set to the command name
		private bool CommandViewNameIsEqual(DbViewState that)
		{
			string v1 = (this.ViewDefName.Length == 0) ? this.Command.ToString() : this.ViewDefName;
			string v2 = (that.ViewDefName.Length == 0) ? that.Command.ToString() : that.ViewDefName;
			return (v1 == v2);
		}

		string command = "";
		string connectionTag = "";
		string filter = "";
		string viewDefinitionName = "";
		int rowIndex = 0;
		int sequence = 0;
	}

	// Specialised consumner of the more general purpose History class
	class DbViewStateHistory
	{
		// delegate
		public DbViewState NavigateBack(bool uselessParam) { return this.history.Back(); }

		// delegate
		public bool IsEmpty { get { return this.history.IsEmpty; } }

		// Add is misnamed. Should be something like RegisterGridStateUse()
		public void Add(DbViewState state, bool isWanted)
		{
			// This will add the state if necessary and update the stack if necessary
			this.history.UpdateHistory(state, isWanted);
		}

		public DataTable GetHistory() { return this.MakeHistoryTable(); }

		// Selection from pop-up menu. 
		public DbViewState NavigateBackTo(String memo, bool uselessParam)
		{
			// unpack the memo
			DbViewState state = this.StateFromMemoString(memo);
			// the Sequence is the stack pointer
			// register the change in position on the stack
			this.history.NavigateTo(state.Sequence);
			// send back the state.
			return state;
		}


		// This is for the history report
		public DataTable MakeHistoryTable()
		{
			// configure the output
			DataTable history = DataTableHelpers.MakeDataTable("History", "id|Connection|Type|View|Text|P1|P2|P3", "ISSSSSSS");

			for (int vi = 0; vi < this.history.Count(History<DbViewState>.IndexType.visitorder); ++vi)
			{
				// obtain the relevant state
				DbViewState state = this.history.GetAt(vi, History<DbViewState>.IndexType.visitorder);
				DataRow dr = history.NewRow();
				// populate from the current state
				MakeHistorydataRow(state, dr);
				dr[0] = vi + 1; // Not right . needs dereferencing
				history.Rows.Add(dr);
			}
			// sort is possibly not needed any more
			return DataTableHelpers.MakeSortedTable(history, "id", true);
		}

		private static void MakeHistorydataRow(DbViewState state, DataRow dr)
		{
			dr[1] = state.ConnectionTag;
			dr[2] = state.Command.ToString();
			dr[3] = (state.ViewDefName == state.Command.ToString()) ? "" : state.ViewDefName;
			dr[4] = state.Filter;
			int col = 5;
			// show as many parameters as the grid allows.
			foreach (String p in state.Command.GetParamInfo())
			{
				dr[col++] = p;
				if (col >= dr.ItemArray.GetLength(0)) break; // enough.
			}
		}

		public DbViewState StateFromHistoryRow(DataRow dr)
		{
			DbViewState prevState = this.StateFromHistoryRowAlt(dr);
			if (prevState != null) return prevState; // preferred.
			// fall through to old "buggy" style hopefully never.
			// see datatable creation
			string Connection = dr[1].ToString();
			string Command = dr[2].ToString();
			string ViewName = dr[3].ToString();
			string Filter = dr[4].ToString();
			DbViewState pastState = new DbViewState(Command, Connection, Filter, ViewName, 0);
			prevState = this.FindMatchingState(pastState);
			return (prevState == null) ? pastState : prevState;
		}

		private DbViewState StateFromHistoryRowAlt(DataRow dr)
		{
			// use Id to get state from history
			int selectedId;
			if (int.TryParse(dr[0].ToString(), out selectedId) == false)
				// Not what we expected in the Id column
				return null;
			return this.history.GetAt(selectedId - 1, History<DbViewState>.IndexType.visitorder);
		}

		DbViewState FindMatchingState(DbViewState target)
		{
			foreach (DbViewState s in this.history.GetHistory())
			{
				if (s.Equals(target))
					return s;
			}
			return null;
		}

		public void UpdateRowIndex(Command currentComand, int indexValue)
		{
			if (this.history.IsEmpty) return; // first hit timing problem bypassed

			DbViewState current = this.history.GetAt(this.history.StackPointer, History<DbViewState>.IndexType.stack);
			// test commands to bypass the other timing problem where the grid is displayed and feeds back
			// row zero before the new state is added to the stack.
			if (IsSameTypeOfCommand(current.Command, currentComand))
			{
				current.RowIndex = indexValue;
			}
		}

		private bool IsSameTypeOfCommand(Command command, Command currentComand)
		{
			if (command.Cmd != currentComand.Cmd)
				return false; // obviously not.
			if (command.Cmd != Commands.Cmnd.Extension)
				return true;  // two objects with same type, but not extension are SameType
			return (command.Name == currentComand.Name); // the Namecheck is the tiebreaker
		}

		// drop down history list. 
		public String[] GetHistoryMemoStrings(out int currentItem)
		{
			currentItem = this.history.StackPointer;
			List<String> memoStrings = new List<string>();
			int stackCount = this.history.Count(History<DbViewState>.IndexType.stack);
			for (int stackIndex = 0; stackIndex < stackCount; ++stackIndex)
				memoStrings.Add(this.MakeMemoString(this.history.GetAt(stackIndex, History<DbViewState>.IndexType.stack), stackIndex + 1)); // param 2 is 0 to 1-based.
			return memoStrings.ToArray();
		}

		// unfortunately public
		public DbViewState StateFromMemoString(String memoString)
		{
			int iPos = memoString.IndexOf(":");  // see previous
			string stackIndexStr = memoString.Substring(0, iPos); // this is a stack pointer.
			int stackIndex = Convert.ToInt32(stackIndexStr);
			DbViewState state = this.history.GetAt(stackIndex - 1, History<DbViewState>.IndexType.stack); // index was memoised as 1-based.
			state.Sequence = stackIndex - 1; // this info is required by the caller
			return state;
		}

		// These memo strings must just encode the stack position and look pretty
		string MakeMemoString(DbViewState state, int stackIndex)
		{
			string viewName = (state.Command.ToString() == state.ViewDefName) ? "" : state.ViewDefName;
			string filter = (state.Filter.Length > 0) ? "(" + state.Filter + ")" : "";
			string extra = (viewName.Length > 0) ? string.Format("[{0}{1}]", viewName, filter) : filter;
			return string.Format("{3}:{0}.{1}{2}", state.ConnectionTag, state.Command, extra, stackIndex);
		}


		History<DbViewState> history = new History<DbViewState>();
	}

	/*
		Generic class to encapsulate a navigated History. Manages
		a list of objects plus
	*/
	class History<T>
	{
		public History()
		{
			this.theHistory = new List<T>();
			this.theStack = new List<int>();
			this.visitOrder = new List<int>();
			this.theStackPointer = -1;
			this.lastItemWasWanted = true;
		}

		// 
		public enum IndexType { direct, stack, visitorder }

		public bool IsEmpty { get { return (this.theStack.Count == 0); } }

		public int StackPointer { get { return this.theStackPointer; } }

		// Because I'm too lazy to implement IEnumerable
		// also because there are different collections to count.
		public int Count(IndexType idxtype)
		{
			return (idxtype == IndexType.direct) ? this.theHistory.Count :
					(idxtype == IndexType.visitorder) ? this.theHistory.Count :
					this.theStack.Count;
		}
		// 
		public T GetAt(int index, IndexType idxtype)
		{
			// sanity
			if (index < 0) throw new IndexOutOfRangeException(String.Format("Index {0} is out of range", index));
			if (idxtype == IndexType.direct && index >= this.theHistory.Count) throw new IndexOutOfRangeException(String.Format("Index {0} is out of range for history", index));
			if (idxtype == IndexType.stack && index >= this.theStack.Count) throw new IndexOutOfRangeException(String.Format("Index {0} is out of range for the history stack", index));
			if (idxtype == IndexType.direct)
				return this.theHistory[index];
			else if (idxtype == IndexType.visitorder)
				return this.theHistory[this.visitOrder[index]];
			else
				return this.theHistory[this.theStack[index]];
		}

		public void UpdateHistory(T o, bool isWantedInHistory)
		{
			this.lastItemWasWanted = isWantedInHistory;
			if (!this.lastItemWasWanted) return;			// just setting this flag is enough
			if (this.theStackPointer != -1 && o.Equals(this.theHistory[this.theStack[this.theStackPointer]]))
				return; // just setting the flag is enough. History is alreay where we want it.
			int ptr = this.Add(o);
			// now the stack
			// adding while in the middle of navigaing history. In this case
			// clear all stack items above.
			if (this.theStackPointer > 0)
			{
				this.theStack.RemoveRange(0, this.theStackPointer);
				this.theStackPointer = 0;
			}
			this.theStack.Insert(0, ptr);
			this.visitOrder.Remove(ptr);
			this.visitOrder.Insert(0, ptr);
			this.theStackPointer = 0;
		}

		public int Add(T o)
		{
			// if not in history add it
			// if it is then just find the pointer to it
			int ptr = this.theHistory.IndexOf(o);
			if (ptr == -1)
			{
				this.theHistory.Add(o);
				this.visitOrder.Add(this.theHistory.Count - 1); // this will be killed
				ptr = this.theHistory.Count - 1;
			}
			return ptr;
		}

		public T Back()
		{
			// if last registered grid was not put into history
			// do not wind back the pointer, but do clear the flag
			if (!this.lastItemWasWanted)
			{
				this.lastItemWasWanted = true;
				return this.GetCurrent();
			}

			if (this.theStackPointer < this.theStack.Count - 1)
			{
				++this.theStackPointer;
			}
			return this.GetCurrent();
		}

		public T NavigateTo(int stackIndex)
		{
			if (stackIndex >= 0 && stackIndex < this.theStack.Count)
			{
				this.theStackPointer = stackIndex;
			}
			return this.GetCurrent();
		}

		private T GetCurrent()
		{
			int itemIdx = this.theStack[this.theStackPointer];
			T item = this.theHistory[itemIdx];
			this.visitOrder.Remove(itemIdx);
			this.visitOrder.Insert(0, itemIdx);
			return item;
		}

		public T[] GetHistory()
		{
			return this.theHistory.ToArray();
		}

		public int[] GetStack()
		{
			return this.theStack.ToArray();
		}

		List<T> theHistory;
		List<int> theStack;
		List<int> visitOrder;
		int theStackPointer;
		bool lastItemWasWanted;
	}
}
